Repository 設計模式主要是要分離商業邏輯與資料存取的邏輯,希望開發者專注在商業邏輯的設計,不必擔心如何與資料庫介接。
圖一. DDD 分層
另外,領域驅動設計(DDD)強調領域物件(Domain Object)的重要性,資料庫的存取邏輯應在領域物件類別內實踐,微軟的DDD文件指引主張一個Aggregate應該定義一個Repository,個別管理Aggregate內所有的實體(Entity),架構如下圖。
圖二. Aggregate 的 Repository 設計模式,圖片來源:微軟的DDD文件指引
上圖的Data Tier對應ORM模型,透過模型物件存取資料庫,而Repository就架在這一層之上,進行相關的 Domain Object 操作,一個Domain Object通常會包含多個資料表的操作。例如輸入一張訂單,至少包括兩個資料表--表頭(Order)、表身(Order details),如下圖,表頭包括客戶名稱、送貨地址、訂單日期、交貨日期等,表身包括多筆資料,每一筆含商品名稱、單價、數量、折扣等。
圖三. 訂單樣本,圖片來源:手寫統一發票計算機 - 工具邦
以下我們就以Django套件來實踐Repository設計模式。
Django 安裝非常簡單,執行下列指令:
pip install django
測試專案可至這裡下載,專案目錄為test1,資料庫為db.sqlite3,預設為SQLite資料庫。
Django要求每個資料表都要建立一個對應的類別,例如客戶資料表:
from django.db import models
class Customer(models.Model):
customer_id = models.CharField(max_length=20)
customer_name = models.CharField(max_length=100)
contact = models.CharField(max_length=30, blank=True, null=True)
address = models.CharField(max_length=120, blank=True, null=True)
每個應用系統大概都會有數十個,乃至數百個資料表,若要賦予每個資料表『新增/查詢/更正/刪除(CRUD)』功能,如果每個類別分別撰寫,不僅費時費工,而且不易維護,因此,我們可以應用Repository設計模式,完成上述的功能,程式碼如下,檔名為repository.py:
class Repository:
# 初始化,指定類別名稱
def __init__(self, class1):
self.class1 = class1
# 單筆新增
def create(self, **dict1):
obj = self.class1()
for key in dict1.keys():
setattr(obj, key, dict1[key])
obj.save()
return obj
# 多筆新增
def bulk_create(self, objs):
return self.class1.objects.bulk_create(objs)
# 存檔(新增或更新)
def save(self, obj):
return obj.save()
# 刪除
def delete(self, pk):
return self.class1.objects.filter(pk=pk).delete()
# 刪除2
def delete_object(self, obj):
return obj.delete()
# 單筆查詢
def get_one(self, pk):
return self.class1.objects.get(pk=pk)
# 多筆查詢
def get_many(self, **filter):
return self.class1.objects.filter(**filter)
使用 Django shell 測試每一個功能,先在專案目錄test1,啟動 shell:
python manage.py shell
from sales.models import *
from repository import *
# 指定類別名稱
repo = Repository(Customer)
# 單筆查詢,query by pk
cust = repo.get_one(2)
print(cust.customer_name)
順利取得 pk=2 的資料列,顯示customer_00002。pk為資料表主鍵(Primary Key),為自動給號。
查詢customer_name含0000,而且id>=5。
obj_list = repo.get_many(customer_name__contains='0000', id__gte=5)
for obj in obj_list:
print(obj.customer_name)
顯示5筆資料。
customer_00005
customer_00006
customer_00007
customer_00008
customer_00009
i=11
obj = Customer()
obj.customer_id=f'{i:05d}'
obj.customer_name=f'customer_{i:05d}'
obj.contact=f'contact_{i:05d}'
obj.address=f'address_{i:05d}'
repo.save(obj)
print(obj.id)
顯示物件的pk=11。
repo.delete(11)
i=12
obj=repo.create(customer_id=f'{i:05d}',
customer_name=f'customer_{i:05d}',
contact=f'contact_{i:05d}',
address=f'address_{i:05d}',
)
print(obj.id)
顯示物件的pk=12。
repo.delete_object(obj)
obj = repo.get_one(1)
obj.customer_id=f'XXX'
repo.save(obj)
pk=1的customer_id被改為'XXX',可再次查詢求證。
一次新增十筆資料。
obj_list = []
for i in range(11, 21):
obj = Customer()
obj.customer_id=f'{i:05d}'
obj.customer_name=f'customer_{i:05d}'
obj.contact=f'contact_{i:05d}'
obj.address=f'address_{i:05d}'
obj_list.append(obj)
repo.bulk_create(obj_list)
為簡化說明,repository.py 並未作例外控制及檢查,讀者可自行添加,例如添加一筆資料前先檢查資料是否重覆。
一個Domain Object通常會包含多個資料表的操作。例如輸入一張訂單,至少包括兩個資料表 -- 表頭(Order)、表身(Order_detail),必須作好交易管理(Transaction),Django 提供很好的機制,可參考官網說明。
建立測試函數如下,可新增一筆訂單,存檔名稱為 repository_do.py:
from sales.models import *
from repository import *
from django.db import transaction
# order save
@transaction.atomic
def create_order(cust):
repo = Repository(Order)
ord=repo.create(order_id='so_0001',
customer_id=cust,
order_date='20210801',
required_date='20210901',
)
# 多筆新增
repo_detail = Repository(Order_Detail)
repo_product = Repository(Product)
obj_list = []
for i in range(1, 4):
obj = Order_Detail()
obj.order_id=ord
obj.product_id=repo_product.get_one(i)
obj.unit_price=100
obj.quantity=5
obj.discount=0
obj_list.append(obj)
repo_detail.bulk_create(obj_list)
其中 @transaction.atomic 可以控制交易,如果 create_order() 內有任何錯誤,系統會自動 rollback,全部資料均不會寫入資料庫。
測試程式碼如下:
from sales.models import *
from repository import *
from repository_do import *
# 取得客戶物件
repo = Repository(Customer)
cust = repo.get_one(2)
# 新增一筆訂單,含表頭(Order)、表身(Order_detail)
create_order(cust)
檢查資料庫,sales_order、sales_order_detail 確實都寫入資料了。
Repository 設計模式結合 ORM,可以大幅提高開發的速度,筆者曾經開發一個購物平台的前/後台,只花了兩個月的時間,而且案主也非常滿意,再結合 DDD 的設計概念,就更給力了。